package agent;

import common.DominoState;
import java.util.LinkedList;
import common.Piece;
import common.Side;
import common.Move;
import java.util.Collections;
import java.util.Random;

/**
 * Represents what the agent knows about the current state of the game and,
 * maybe, about the game history. Sugestions: - We sugest the implementation of
 * a method that returns the list of pieces that the opponent may have: Set of
 * all domino pieces - Already played pieces - Current agent pieces - Maybe
 * something else. - Important: implement a method that returns an
 * AgentSearchState version of the current game state (this state). - Other
 * aspects you may consider necessary.
 */
public class AgentCurrentState extends AgentState {

    //lastPlayer -> se sou eu a jogar e no lastPLayer tou eu então quer dizer que podemos retirar das peças possiveis algumas peças
    private int numberOfOpponentPieces = DominoState.PIECES_PER_PLAYER;
    private LinkedList<Piece> opponentPossiblePieces;
    private Player lastPlayer = Player.NONE;

    private int[] probabilities;
//    private Player currentPlayer;
    //qd peçasPossiveis.size == numPeçasAdversario -> sabemos o jogo do adversario e ja nao precisamos de projecções

    public AgentCurrentState(LinkedList<Piece> agentPieces) {
        super(agentPieces);
        opponentPossiblePieces = getAllPiecesList();
        opponentPossiblePieces.removeAll(agentPieces);
        probabilities = new int[]{10, 10, 10, 10, 10, 10, 10};
    }

    /**
     * Places a piece of the given player in the given side of the list of
     * already placed pieces. It should be used to update the current state as
     * known by the agent (see atribute currentState in class Agent). It is not
     * meant to be used to place pieces during the search process (at least in
     * is present version). We advise students to implement a different method
     * in class AgentSearchState.
     *
     * @param player the player that places the piece.
     * @param side the side of the list of already played pieces where the piece
     * must be placed.
     * @param piece the piece to be placed.
     * @return true if the piece can be placed (valid move) and false,
     * otherwise.
     */
    public boolean placePiece(Player player, Side side, Piece piece) {

        if (lastPlayer == Player.AGENT && player == Player.AGENT) {
            recalcOpponentPossiblePiecesBasedOnPlayerSkip();
        }
        byte insideValue = Side.LEFT == side ? leftValue : rightValue;
        if (super.placePiece(side, piece)) {
            if (player == Player.AGENT) {
                agentPieces.remove(piece);

            } else {
                ajustProbabilities(piece, insideValue);
                numberOfOpponentPieces--;
                opponentPossiblePieces.remove(piece);
            }
            lastPlayer = player;
            return true;
        }
        return false;
    }

    private void ajustProbabilities(Piece piece, byte insideValue) {
        int sameDots = 0;
        for (Piece p : opponentPossiblePieces) {
            if (p.hasValue(piece.getOppositeValue(insideValue))){
                sameDots++;
            }
        }

        if (sameDots / opponentPossiblePieces.size() > 0.3) { //se ele tem muitas peças (muitas peças => + de 30%) do mesmo tipo
               probabilities[piece.getOppositeValue(insideValue)] += 5;
           }
        
        for (Piece p : opponentPossiblePieces) { //se ele jogou uma peça com menos pontos, qd tinham uma peça que podia livrar mais pontos
            if (p.getValue1() == insideValue || p.getValue2() == insideValue) {
                if (p.getOppositeValue(insideValue) > piece.getOppositeValue(insideValue)) {
                    probabilities[p.getOppositeValue(insideValue)] /= 2;
                }
            }

        }
    }

    private void recalcOpponentPossiblePiecesBasedOnPlayerSkip() {
        LinkedList<Piece> aux = (LinkedList<Piece>) opponentPossiblePieces.clone();
        for (Piece p : aux) {
            if (p.getValue1() == leftValue || p.getValue1() == rightValue
                    || p.getValue2() == leftValue || p.getValue2() == rightValue) {
                this.opponentPossiblePieces.remove(p);
            }
        }
    }

    //Used by the random algorithm
    public LinkedList<Move> getAgentPossibleMoves() {
        LinkedList<Move> possibleMoves = new LinkedList<>();
        for (Piece piece : agentPieces) {
            if (isValidMove(Side.LEFT, piece)) {
                Move m = new Move(Side.LEFT, piece);
                possibleMoves.add(m);
            }
            if (isValidMove(Side.RIGHT, piece)) {
                Move m = new Move(Side.RIGHT, piece);
                possibleMoves.add(m);
            }
        }
        return possibleMoves;
    }

    public int getNumberOfOpponentPieces() {
        return numberOfOpponentPieces;
    }

    //criar metodo getOpponentPossiblePices
    public LinkedList<Piece> getOpponentPossiblePieces() {
        return opponentPossiblePieces;
    }

    public LinkedList<AgentSearchState> buildGuessedCurrentStates(int handsLimit, Random random) {
        LinkedList<AgentSearchState> guessedCurrentStates = new LinkedList<>();
        int numPossibleOpponentHands = Combinations.getNumCombinations(opponentPossiblePieces.size(), numberOfOpponentPieces);
        int numHands = handsLimit < numPossibleOpponentHands ? handsLimit : numPossibleOpponentHands;
        LinkedList<int[]> combinations
                = Combinations.computeCombinations(opponentPossiblePieces.size(), numberOfOpponentPieces);
        
        float ratio = (float)numHands / combinations.size();
        if (ratio > 0.8) {
            Collections.shuffle(combinations, random);
            for (int i = 0; i < numHands; i++) {
                LinkedList<Piece> guessedPieces = new LinkedList<>();
                for (int j = 0; j < numberOfOpponentPieces; j++) {
                    guessedPieces.add(opponentPossiblePieces.get(combinations.get(i)[j]));
                }
                guessedCurrentStates.add(getAgentSearchState(guessedPieces));
            }
        } else {
            //el tournament
            int tournamentSize = combinations.size() / 6;
            LinkedList<AgentSearchState> aux;
            int bestValue = 0;
            int[] values = new int[tournamentSize];
            for (int i = 0; i < numHands; i++) {
                Collections.shuffle(combinations, random);
                aux = new LinkedList<>();
                for (int j = 0; j < tournamentSize; j++) {
                    LinkedList<Piece> guessedPieces = new LinkedList<>();
                    for (int k = 0; k < numberOfOpponentPieces; k++) {
                        Piece p = opponentPossiblePieces.get(combinations.get(j)[k]);
                        guessedPieces.add(p);
                        values[j] += probabilities[p.getValue1()] + probabilities[p.getValue2()];
                    }
                    aux.add(getAgentSearchState(guessedPieces));
                }
                int index = 0;
                bestValue = values[0];
                for (int j = 1; j < values.length; j++) {
                    if (values[j] > bestValue) {
                        index = j;
                        bestValue = values[j];
                    }                    
                }
                guessedCurrentStates.add(aux.get(index));
                combinations.remove(index);
            }
        }
        //escolher a mão de acordo com as probabilidades e devolver APENAS UMA MAO!!!! 
        return guessedCurrentStates;
    }

    public AgentSearchState getAgentSearchState(LinkedList<Piece> opponentPieces) {
        return new AgentSearchState(
                (LinkedList<Piece>) agentPieces.clone(),
                (LinkedList<Piece>) opponentPieces.clone(),
                leftValue, rightValue, Player.AGENT);
    }

}
